using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using Microsoft.Cryptography.Diagnostics;

namespace Microsoft.Cryptography
{
    internal class RsaCalculator
    {
        #region enumerations and const

        /// <summary>
        /// RSADir determines whether we are encrypting or decrypting.
        /// </summary>
        public enum RsaDir
        {
            /// <summary>Encryption</summary>
            Encrypt,
            /// <summary>Decryption</summary>
            Decrypt
        }
        /// <summary>
        /// Determine if we are using a RsaPublic or a RsaPrivate key
        /// </summary>
        public enum KeyType
        {
            /// <summary>Encrypt or Decrypt using RSA Public key</summary>
            RsaPublic,
            /// <summary>Encrypt or Decrypt using RSA Private key</summary>
            RsaPrivate
        }

        #endregion

        #region constructors

        /// <summary>
        /// Empty constructor.
        /// </summary>
        public RsaCalculator()
        {
        }

        /// <summary>
        /// Contructor with import of RSA parameters for the rsa calculation.
        /// </summary>
        /// <param name="rsaParams">RSA POarameters to import.</param>
        public RsaCalculator(RSAParameters rsaParams)
        {
            AddRsaParamters(rsaParams);
        }

        #endregion

        #region private fields and public properties

        /// <summary>
        /// RSA parameter.
        /// </summary>
        private RSAParameters _rsaParams;

        /// <summary>
        /// RSA parameter.
        /// </summary>
        public RSAParameters RsaParams
        {
            get { return _rsaParams; }
            set { _rsaParams = value; }
        }		

        /// <summary>
        /// Keep the bytes as they were before encryption/decryption
        /// </summary>
        public byte[] _bytesInput;
        public byte[] BytesInput { get { return _bytesInput; } }
        /// <summary>
        /// Keep the bytes as they were after encryption/decryption
        /// </summary>
        public byte[] _bytesOutput;
        public byte[] BytesOutput { get { return _bytesOutput; } }

        #endregion

        /// <summary>
        /// Import the RSA parameters.
        /// </summary>
        /// <param name="rsaParams">RSA Parameters to import.</param>
        public void AddRsaParamters(RSAParameters rsaParams)
        {
            _rsaParams = rsaParams;
        }

        /// <summary>
        /// Do the Encryption or Decyption as specified
        /// </summary>
        /// <param name="keytyp">Which key type to use</param>
        /// <param name="dir">Encryption or Decryption</param>
        /// <param name="msg">The message to encrypt/decrypt</param>
        /// <returns>The Encrpyted/Decrypted message</returns>
        public string DoCrypt(KeyType keytyp, RsaDir dir, string msg)
        {
            string rv;
            if (keytyp == KeyType.RsaPrivate) rv = DoCryptPri(dir, msg);
            else rv = DoCryptPub(dir, msg);
            return rv;
        }

        /// <summary>
        /// Do Encyption or Decryption with with the Private key
        /// </summary>
        /// <param name="dir">Encryption or Decryption</param>
        /// <param name="msg">The message to encrypt/decrypt</param>
        /// <returns>The Encrpyted/Decrypted message</returns>
        public string DoCryptPri(RsaDir dir, string msg)
        {
            //if (dir == RsaDir.Decrypt)
            //{
                _bytesInput = ByteTools.FromBinHex(msg);
            //}
            //else
            //{
            //    _bytesInput = (new System.Text.ASCIIEncoding()).GetBytes(msg);
            //    _bytesInput = RSApadding(0x80, _bytesInput);
            //    //bytesInput = ByteTools.AddZeros( bytesInput, 32-msg.Length );
            //}

            FlexInt biM = new FlexInt(_bytesInput);
            FlexInt biN = new FlexInt(_rsaParams.Modulus);
            FlexInt biD = new FlexInt(_rsaParams.D);

            System.Diagnostics.Stopwatch watchModPow = new System.Diagnostics.Stopwatch();
            watchModPow.Start();

            FlexInt biO = biM.ModPow(biD, biN);   // do it!

            watchModPow.Stop();
            LogProvider.LogMessage(LogProvider.LogPriorities.Info,
                LogProvider.LogSources.CRYPTO_Basis,
                string.Format("DoCryptPri.ModPow: {0} ms", watchModPow.ElapsedMilliseconds));


            _bytesOutput = biO.GetBytes();
            _bytesOutput = SlideForwardFromZeroByte(_bytesOutput);

            //char[] car = ByteTools.CvtToChr(_bytesOutput);  // just to look at in debugger
            string code;
            if (dir == RsaDir.Encrypt)
            {
                code = ByteTools.ToBinHex(_bytesOutput, "X");
            }
            else
            {
                code = (new System.Text.ASCIIEncoding()).GetString(_bytesOutput);
            }
            return code;
        }

        /// <summary>
        /// Do Encyption or Decryption with the Public key
        /// </summary>
        /// <param name="dir">Encryption or Decryption</param>
        /// <param name="msg">The message to encrypt/decrypt</param>
        /// <returns>The Encrpyted/Decrypted message</returns>
        public string DoCryptPub(RsaDir dir, string msg)
        {
            if (dir == RsaDir.Decrypt)
            {
                _bytesInput = ByteTools.FromBinHex(msg);
            }
            else
            {
                _bytesInput = (new System.Text.ASCIIEncoding()).GetBytes(msg);
                //				bytesInput = ByteTools.AddZeros( (new System.Text.ASCIIEncoding()).GetBytes(msg),0 );
            }

            if (_bytesInput.Length < 0x80)
            {
                _bytesInput = RSApadding(0x80, _bytesInput);
            }
            FlexInt biM = new FlexInt(_bytesInput);
            FlexInt biN = new FlexInt(_rsaParams.Modulus);
            FlexInt biE = new FlexInt(_rsaParams.Exponent);

            System.Diagnostics.Stopwatch watchModPow = new System.Diagnostics.Stopwatch();
            watchModPow.Start();
            FlexInt biC = biM.ModPow(biE, biN);   // do it!
            watchModPow.Stop();

            LogProvider.LogMessage(LogProvider.LogPriorities.Info,
                LogProvider.LogSources.CRYPTO_Basis,
                string.Format("DoCryptPub.ModPow: {0} ms", watchModPow.ElapsedMilliseconds));

            
            _bytesOutput = biC.GetBytes();
            string code1 = (new System.Text.UnicodeEncoding()).GetString(_bytesOutput);
            
            string code;
            if (dir == RsaDir.Encrypt)
            {
                code = ByteTools.ToBinHex(_bytesOutput, "X");
            }
            else
            {
                code = (new System.Text.ASCIIEncoding()).GetString(_bytesOutput);
            }
            return code;
        }

        /// <summary>
        /// Add OAEP#1 RSA padding to a byte array.
        /// </summary>
        /// <param name="nneed">How big the array should get.</param>
        /// <param name="bar">The byte array to pad</param>
        /// <returns>The padded Array</returns>
        public byte[] RSApadding(int nneed, byte[] bar)
        {
            // padding according to OAEP#1
            int nranpad = nneed - 3 - bar.Length;
            if (nranpad < 8) throw new ApplicationException("Data too long for OLAP padding");
            byte[] rv = new byte[nneed];
            int ir = 0;
            rv[ir++] = 0;
            rv[ir++] = 2;
            Random ranman = new Random();
            for (int i = 0; i < nranpad; i++)
            {
                byte ranbyte = 0;
                while (ranbyte <= 6) // no small pads
                {
                    ranbyte = (byte)(ranman.Next(256) + 1);
                }
                rv[ir++] = ranbyte;
            }
            rv[ir++] = 0;
            for (int i = 0; i < bar.Length; i++)
            {
                rv[ir++] = bar[i];
            }
            return rv;
        }

        /// <summary>
        /// This removes the OAEP#1 padding. 
        /// </summary>
        /// <remarks>
        /// The name comes from the processing. 
        /// The payload string is at the end of the decrypted block.
        /// It starts with the first byte after the It basically looks for the first
        /// zero byte from the end. We look for this byte, and move it to the begining
        /// of the block.
        /// If no zero byte is found, we just return the whole block.
        /// </remarks>
        /// <param name="bar">input bytes</param>
        /// <returns>output bytes</returns>
        public byte[] SlideForwardFromZeroByte(byte[] bar)
        {
            int countZeroBytes = 0;
            for (int i = 0; i < bar.Length; i++)
            {
                if (bar[i] == 0)
                {
                    countZeroBytes++;
                }
                else
                {
                    break;
                }
            }
            byte[] rv = new byte[bar.Length - countZeroBytes];
            Array.Copy(bar, countZeroBytes, rv, 0, rv.Length);
            return rv;
        }
    }
}
